package detective.task;
import static io.netty.handler.codec.http.HttpHeaders.getHost;
import static io.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;
import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
import static io.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.util.CharsetUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import detective.common.annotation.ThreadSafe;
import detective.core.Parameters;
import detective.core.TestTask;
import detective.core.config.ConfigException;
import detective.core.dsl.ParametersImpl;
@ThreadSafe
public class HttpServerTask implements TestTask {
public interface ServerHandler{
void close();
}
public Parameters execute(Parameters config) throws ConfigException {
if (config == null)
throw ConfigException.configCantEmpty();
if (config.get("httpserver.port") == null)
throw new ConfigException("httpserver.port can't be empty, it's a number for example 8080");
Integer port = Integer.parseInt(config.get("httpserver.port").toString());
// Configure the server.
final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(
new HttpSnoopServerInitializer());
try {
Channel ch = b.bind(port).sync().channel();
ChannelFuture future = ch.closeFuture();
Parameters result = new ParametersImpl();
result.put("channel", future);
result.put("handler", new ServerHandler(){
public void close() {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}});
return result;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
} finally {
//Close in handler
}
}
public class HttpSnoopServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// Create a default pipeline implementation.
ChannelPipeline p = ch.pipeline();
// Uncomment the following line if you want HTTPS
// SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
// engine.setUseClientMode(false);
// p.addLast("ssl", new SslHandler(engine));
p.addLast("decoder", new HttpRequestDecoder());
// Uncomment the following line if you don't want to handle HttpChunks.
// p.addLast("aggregator", new HttpObjectAggregator(1048576));
p.addLast("encoder", new HttpResponseEncoder());
// Remove the following line if you don't want automatic content compression.
// p.addLast("deflater", new HttpContentCompressor());
p.addLast("handler", new HttpSnoopServerHandler());
}
}
public static class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object> {
private HttpRequest request;
/** Buffer that stores the response content */
private final StringBuilder buf = new StringBuilder();
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest request = this.request = (HttpRequest) msg;
if (is100ContinueExpected(request)) {
send100Continue(ctx);
}
buf.setLength(0);
buf.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
buf.append("===================================\r\n");
buf.append("VERSION: ").append(request.getProtocolVersion()).append("\r\n");
buf.append("HOSTNAME: ").append(getHost(request, "unknown")).append("\r\n");
buf.append("REQUEST_URI: ").append(request.getUri()).append("\r\n\r\n");
HttpHeaders headers = request.headers();
if (!headers.isEmpty()) {
for (Map.Entry<String, String> h : headers) {
String key = h.getKey();
String value = h.getValue();
buf.append("HEADER: ").append(key).append(" = ").append(value).append("\r\n");
}
buf.append("\r\n");
}
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
Map<String, List<String>> params = queryStringDecoder.parameters();
if (!params.isEmpty()) {
for (Entry<String, List<String>> p : params.entrySet()) {
String key = p.getKey();
List<String> vals = p.getValue();
for (String val : vals) {
buf.append("PARAM: ").append(key).append(" = ").append(val).append("\r\n");
}
}
buf.append("\r\n");
}
appendDecoderResult(buf, request);
}
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
ByteBuf content = httpContent.content();
if (content.isReadable()) {
buf.append("CONTENT: ");
buf.append(content.toString(CharsetUtil.UTF_8));
buf.append("\r\n");
appendDecoderResult(buf, request);
}
if (msg instanceof LastHttpContent) {
buf.append("END OF CONTENT\r\n");
LastHttpContent trailer = (LastHttpContent) msg;
if (!trailer.trailingHeaders().isEmpty()) {
buf.append("\r\n");
for (String name : trailer.trailingHeaders().names()) {
for (String value : trailer.trailingHeaders().getAll(name)) {
buf.append("TRAILING HEADER: ");
buf.append(name).append(" = ").append(value).append("\r\n");
}
}
buf.append("\r\n");
}
writeResponse(trailer, ctx);
}
}
}
private static void appendDecoderResult(StringBuilder buf, HttpObject o) {
DecoderResult result = o.getDecoderResult();
if (result.isSuccess()) {
return;
}
buf.append(".. WITH DECODER FAILURE: ");
buf.append(result.cause());
buf.append("\r\n");
}
private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
// Decide whether to close the connection or not.
boolean keepAlive = isKeepAlive(request);
// Build the response object.
FullHttpResponse response =
new DefaultFullHttpResponse(HTTP_1_1, currentObj.getDecoderResult().isSuccess() ? OK
: BAD_REQUEST, Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
if (keepAlive) {
// Add 'Content-Length' header only for a keep-alive connection.
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
// Add keep alive header as per:
// - http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
// Encode the cookie.
String cookieString = request.headers().get(COOKIE);
if (cookieString != null) {
Set<Cookie> cookies = CookieDecoder.decode(cookieString);
if (!cookies.isEmpty()) {
// Reset the cookies if necessary.
for (Cookie cookie : cookies) {
response.headers().add(SET_COOKIE, ServerCookieEncoder.encode(cookie));
}
}
} else {
// Browser sent no cookie. Add some.
response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key1", "value1"));
response.headers().add(SET_COOKIE, ServerCookieEncoder.encode("key2", "value2"));
}
// Write the response.
ctx.write(response);
return keepAlive;
}
private static void send100Continue(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE);
ctx.write(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
}